﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FlashCards.NeuralNetworks.Utils
{
    public static class NetworkExtensions
    {
        public static Model.NeuralNetwork ToEntity(this NeuralNetworks.NeuralNetwork network)
        {
            var entityNetwork = new Model.NeuralNetwork();
            entityNetwork.NetworkId = network.NetworkId.HasValue ? network.NetworkId.Value : 0;
            entityNetwork.FlashcardSetId = network.FlashcardSetId;

            for (int i = 0; i < network.NeuralLayers.Count; i++)
            {
                var layer = new Model.NeuronsLayer();
                layer.NeuralNetwork = entityNetwork;
                if(network.LayersIds != null)
                    layer.LayerId = network.LayersIds[i];

                for (int j = 0; j < network.NeuralLayers[i].Count; j++)
                {
                    var neuron = network.NeuralLayers[i][j];
                    var entityNeuron = new Model.Neuron();
                    entityNeuron.Order = (short)j;
                    entityNeuron.NeuronGuid = neuron.Guid;

                    if (neuron is NeuralNetworks.ForwardingNeuron)
                    {
                        var forwardingNeuron = neuron as NeuralNetworks.ForwardingNeuron;
                        entityNeuron.IsBias = false;
                        entityNeuron.FlashcardId = forwardingNeuron.FlashcardId;
                    }
                    else if (neuron is NeuralNetworks.BiasNeuron)
                    {
                        var biasNeuron = neuron as NeuralNetworks.BiasNeuron;
                        entityNeuron.IsBias = true;
                    }
                    else
                    {
                        for (int z = 0; z < neuron.Weights.Count; z++ )
                        {
                            var weight = new Model.NeuronWeight
                            {
                                WeightValue = (decimal)neuron.Weights[z],
                                NeuronGuid = neuron.Guid,
                                SourceNeuronGuid = neuron.Inputs[z].Guid,
                            };

                            weight.WeightValue = Math.Round(weight.WeightValue, 10);

                            entityNeuron.NeuronWeights.Add(weight);
                        }
                    }

                    layer.ai_Neurons.Add(entityNeuron);
                }

                entityNetwork.NeuronsLayers.Add(layer);
            }

            return entityNetwork;
        }

        public static Model.NeuralNetwork UpdateNetwork(this Model.NeuralNetwork network, Model.NeuralNetwork databaseNetwork)
        {
            if (network.NetworkId == databaseNetwork.NetworkId)
            {
                foreach (var layer in network.NeuronsLayers)
                {
                    if (databaseNetwork.NeuronsLayers.FirstOrDefault(x => x.LayerId == layer.LayerId) != null)
                    {
                        var dbLayer = databaseNetwork.NeuronsLayers.FirstOrDefault(x => x.LayerId == layer.LayerId);
                        foreach (var neuron in layer.ai_Neurons)
                        {
                            var dbNeuron = dbLayer.ai_Neurons.FirstOrDefault(x => x.NeuronGuid == neuron.NeuronGuid);
                            if (dbNeuron != null)
                            {
                                foreach (var weight in neuron.NeuronWeights)
                                {
                                    var dbWeight = dbNeuron.NeuronWeights.FirstOrDefault(x => x.NeuronGuid == neuron.NeuronGuid && x.SourceNeuronGuid == weight.SourceNeuronGuid);
                                    if (dbWeight != null)
                                    {
                                        dbWeight.WeightValue = weight.WeightValue;
                                    }
                                }
                            }
                            else
                            {
                                dbLayer.ai_Neurons.Add(new Model.Neuron {
                                    IsBias = false, 
                                    NeuronGuid = neuron.NeuronGuid,
                                    Order = (short)(dbLayer.ai_Neurons.Count - 1),
                                    FlashcardId = neuron.FlashcardId
                                });

                                dbLayer.ai_Neurons.FirstOrDefault(x => x.IsBias).Order = (short)(dbLayer.ai_Neurons.Count - 1);
                            }
                        }
                    }
                }
            }
            else
            {
                return null;
            }
            return databaseNetwork;
        }

        public static NeuralNetworks.NeuralNetwork CreateNetwork(this Model.NeuralNetwork entityNetwork)
        {
            var layersIds = entityNetwork.NeuronsLayers.Select(x => x.LayerId).ToList();
            var network = new NeuralNetworks.NeuralNetwork(entityNetwork.NetworkId, layersIds);

            network.LayersNumber = entityNetwork.NeuronsLayers.Count - 1;
            network.OutputsNumber = 4;
            network.InputsNumber = entityNetwork.FlashcardSet.Flashcards.Count;

            network.FlashcardSetId = entityNetwork.FlashcardSetId.Value;

            int layerIndex = 0;
            foreach (var layer in entityNetwork.NeuronsLayers)
            {
                network.NeuralLayers.Add(new List<NeuralNetworks.Neuron>());

                foreach (var neuron in layer.ai_Neurons.OrderBy(x => x.Order))
                {
                    if (neuron.FlashcardId.HasValue)
                    {
                        var input = new NeuralNetworks.ForwardingNeuron(neuron.FlashcardId.Value, neuron.NeuronGuid);
                        network.NeuralLayers[layerIndex].Add(input);
                    }
                    else if (neuron.IsBias)
                    {
                        var bias = new NeuralNetworks.BiasNeuron(neuron.NeuronGuid);
                        network.NeuralLayers[layerIndex].Add(bias);
                    }
                    else
                    {
                        var weights = neuron.NeuronWeights;
                        var hiddenNeuron = new NeuralNetworks.Neuron(weights.Select(x => (double)x.WeightValue).ToList(), neuron.NeuronGuid);

                        hiddenNeuron.Inputs = new List<NeuralNetworks.Neuron>();
                        foreach (var item in weights)
                        {
                            var sourceNeuron = network.NeuralLayers[layerIndex - 1].FirstOrDefault(x => x.Guid == item.SourceNeuronGuid);

                            hiddenNeuron.Inputs.Add(sourceNeuron);
                        }

                        network.NeuralLayers[layerIndex].Add(hiddenNeuron);
                    }
                }
                layerIndex++;
            }


            return network;
        }
    }
}
